using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using MacomberMap.Common.Internals;
using MacomberMap.Common.Components;
using System.Xml;
using System.Drawing.Drawing2D;
using MacomberMap.Common.Database_Interface.CIM.Components;
using MacomberMap.Common.Database_Interface.CIM;

namespace MacomberMap.Common.Database_Interface.CIM.User_Interfaces
{
    /// <summary>
    /// This class allows the user to browse the CIM topology
    /// </summary>
    public partial class frmCIMBrowser : Form
    {

        #region Initialization
        /// <summary>
        /// Initialize a new CIM browser
        /// </summary>
        public frmCIMBrowser()
        {
            InitializeComponent();           
        }

        /// <summary>
        /// Show the CIM browser with a particular element
        /// </summary>
        /// <param name="StartElement"></param>
        public void Show(CIM_Element StartElement)
        {
            AddElement(StartElement);
            base.Show();
            base.Activate();
        }
        #endregion

        #region Variable declarations
        /// <summary>The collection of elements being displayed on the screen</summary>
        public Dictionary<CIM_Element, CIM_Element_FrontEnd> DisplayedElements = new Dictionary<CIM_Element, CIM_Element_FrontEnd>();
        #endregion

        #region User interactions

      
        /// <summary>
        /// Handle the right-click menu on a CIM element
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void CIMElem_MouseClick(object sender, MouseEventArgs e)
        {
            CIM_Element_FrontEnd FrontElement = (sender as CIM_Element_FrontEnd);
            if (e.Button == MouseButtons.Left && (Control.ModifierKeys & Keys.Control) == Keys.Control)
            {
                List<CIM_Element> OutElems = new List<CIM_Element>();
                try { OutElems.AddRange(FrontElement.BaseElement.Elements); }
                catch (Exception) { }
                try { OutElems.AddRange(FrontElement.BaseElement.Nodes); }
                catch (Exception) { }
                foreach (CIM_Element Elem in OutElems)
                    AddElement(Elem);
            }
            else if (e.Button == MouseButtons.Right)
            {
                
                ContextMenuStrip cms = new ContextMenuStrip();
                (cms.Items.Add(FrontElement.BaseElement.rdfID.ToString()) as ToolStripMenuItem).Enabled = false;
                (cms.Items.Add(FrontElement.BaseElement.Type) as ToolStripMenuItem).Enabled = false;
                (cms.Items.Add(FrontElement.BaseElement.Name) as ToolStripMenuItem).Enabled = false;
                if (FrontElement.BaseElement.Substation != null)
                {
                    String LongName = FrontElement.BaseElement.Substation["cim:IdentifiedObject.description"];
                    if (String.IsNullOrEmpty(LongName) || LongName == FrontElement.BaseElement.Substation.Name)
                        (cms.Items.Add("  In substation " + FrontElement.BaseElement.Substation.Name) as ToolStripMenuItem).Enabled = false;
                    else
                        (cms.Items.Add("  In substation " + LongName + " (" + FrontElement.BaseElement.Substation.Name + ")") as ToolStripMenuItem).Enabled = false;
                }
                if (FrontElement.BaseElement.VoltageLevel != null)
                    (cms.Items.Add("  " + FrontElement.BaseElement.VoltageLevel.Name) as ToolStripMenuItem).Enabled = false;
                if (!String.IsNullOrEmpty(FrontElement.BaseElement.TEID))
                    (cms.Items.Add("TEID: " + FrontElement.BaseElement.TEID) as ToolStripMenuItem).Enabled = false;
                cms.Items.Add("-");
                cms.ItemClicked += new ToolStripItemClickedEventHandler(cms_ItemClicked);
                if (FrontElement.BaseElement._InLinks.Count > 0)
                {
                    ToolStripMenuItem NewItem = (ToolStripMenuItem)cms.Items.Add("Incoming (" + FrontElement.BaseElement._InLinks.Count.ToString("#,##0") + ")");
                    NewItem.DropDownOpening += new EventHandler(NewItem_DropDownOpening);
                    NewItem.Tag = FrontElement.BaseElement.InLinks;
                    NewItem.DropDownItems.Add("");
                }

                if (FrontElement.BaseElement._OutLinks.Count > 0)
                {
                    ToolStripMenuItem NewItem = (ToolStripMenuItem)cms.Items.Add("Outgoing (" + FrontElement.BaseElement._OutLinks.Count.ToString("#,##0") + ")");
                    NewItem.DropDownOpening += new EventHandler(NewItem_DropDownOpening);
                    NewItem.Tag = FrontElement.BaseElement.OutLinks;
                    NewItem.DropDownItems.Add("");
                }

                {
                    Dictionary<String, String> OutAttr = BuildAttributes(FrontElement.BaseElement.XML);
                    ToolStripMenuItem NewItem = (ToolStripMenuItem)cms.Items.Add("Attributes (" + OutAttr.Count.ToString("#,##0") + ")");
                    NewItem.DropDownOpening += new EventHandler(NewItem_DropDownOpening);
                    NewItem.Tag = OutAttr;
                    NewItem.DropDownItems.Add("");
                }

                List<CIM_Element> OutElems = new List<CIM_Element>();
                try { OutElems.AddRange(FrontElement.BaseElement.Elements); }
                catch (Exception) { }
                try { OutElems.AddRange(FrontElement.BaseElement.Nodes); }
                catch (Exception) { }
                if (OutElems.Count > 0)
                {
                    cms.Items.Add("-");
                    ToolStripMenuItem NewItem = (ToolStripMenuItem)cms.Items.Add("Elements (" + OutElems.Count.ToString("#,##0") + ")");
                    NewItem.DropDownOpening += new EventHandler(NewItem_DropDownOpening);
                    NewItem.Tag = OutElems.ToArray();
                    NewItem.DropDownItems.Add("");
                }
                cms.Tag = FrontElement.BaseElement;
                cms.Show(FrontElement, e.Location);
            }
        }

        /// <summary>
        /// Handle a new dropdown item opening
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void NewItem_DropDownOpening(object sender, EventArgs e)
        {
            ToolStripDropDownItem DropDownMenu = (ToolStripDropDownItem)sender;
            DropDownMenu.DropDownItems.Clear();
            DropDownMenu.DropDownItemClicked += new ToolStripItemClickedEventHandler(cms_ItemClicked);
            if (DropDownMenu.Tag is CIM_Element[])
            {
                AddDropDownItems(DropDownMenu.Tag as CIM_Element[], DropDownMenu);
                DropDownMenu.DropDownItems.Add("-");
                DropDownMenu.DropDownItems.Add("Add all");
            }
            else if (DropDownMenu.Tag is Dictionary<String, String>)
                AddDropDownItems(DropDownMenu.Tag as Dictionary<String, String>, DropDownMenu);
        }

        private Dictionary<String, String> BuildAttributes(String Xml)
        {
            Dictionary<String, String> OutDic = new Dictionary<string, string>(10);
            int CurPos = 1;
            while ((CurPos = Xml.IndexOf('<', CurPos) + 1) != 0)
            {
                String AttrName = Xml.Substring(CurPos, Xml.IndexOf('>', CurPos) - CurPos);
                if (AttrName.Contains(" "))
                {
                    String Val = AttrName.Substring(AttrName.IndexOf('=') + 2);
                    Val = Val.Substring(0, Val.Length - 2);
                    AttrName = AttrName.Substring(0, AttrName.IndexOf(' '));
                    if (!OutDic.ContainsKey(AttrName))
                        OutDic.Add(AttrName, Val);
                    else
                        for (int a = 0; a < 100; a++)
                            if (!OutDic.ContainsKey(AttrName + "(" + a.ToString() + ")"))
                            {
                                OutDic.Add(AttrName + "(" + a.ToString() + ")", Val);
                                break;
                            }
                }
                else if (AttrName[0] != '/')
                {
                    String Val = Xml.Substring(Xml.IndexOf('>', CurPos) + 1);
                    Val = Val.Substring(0, Val.IndexOf('<'));
                    OutDic.Add(AttrName, Val);
                }
            }
            return OutDic;
        }


        /// <summary>
        /// Add all appropriate drop-down menu elements
        /// </summary>
        /// <param name="Elements">The collection of elements that fit our parameters</param>
        /// <param name="DropDownMenu">The drop down menu to receive the new lines</param>        
        private void AddDropDownItems(CIM_Element[] Elements, ToolStripDropDownItem DropDownMenu)
        {

            //Based on our depth, allocate results accordingly.                                                           
            int CurDepth = GetDepth(DropDownMenu);
            SortedDictionary<String, List<CIM_Element>> Elems = new SortedDictionary<string, List<CIM_Element>>();

            //Go through all elements, and add the appropriate ones into our dictionary
            foreach (CIM_Element Elem in Elements)
            {
                String[] splType = Elem.Type.Split('.', ':');
                if (CurDepth > splType.GetUpperBound(0))
                {                    
                    ToolStripMenuItem NewItem = (ToolStripMenuItem)DropDownMenu.DropDownItems.Add(Elem.rdfID.ToString() + " (" + Elem.Name + ")");
                    if (DisplayedElements.ContainsKey(Elem))
                        NewItem.Enabled = false;
                    NewItem.Tag = Elem;
                }
                else if (CurDepth == 1)
                {
                    if (!Elems.ContainsKey(splType[0] + ":" + splType[1]))
                        Elems.Add(splType[0] + ":" + splType[1], new List<CIM_Element>(10));
                    Elems[splType[0] + ":" + splType[1]].Add(Elem);
                }
                else
                {
                    if (!Elems.ContainsKey(splType[CurDepth]))
                        Elems.Add(splType[CurDepth], new List<CIM_Element>(10));
                    Elems[splType[CurDepth]].Add(Elem);
                }
            }

            foreach (KeyValuePair<String, List<CIM_Element>> Elem in Elems)
            {
                ToolStripMenuItem NewItem = (ToolStripMenuItem)DropDownMenu.DropDownItems.Add(Elem.Key + (Elem.Value.Count > 0 ? " (" + Elem.Value.Count.ToString("#,##0") + ")" : ""));
                NewItem.Tag = Elem.Value.ToArray();
                NewItem.DropDownItems.Add("");
                NewItem.DropDownOpening += new EventHandler(NewItem_DropDownOpening);
                NewItem.DropDownItemClicked += new ToolStripItemClickedEventHandler(cms_ItemClicked);
            }
        }

        /// <summary>
        /// Add all appropriate drop-down menu elements
        /// </summary>
        /// <param name="Elements">The collection of elements that fit our parameters</param>
        /// <param name="DropDownMenu">The drop down menu to receive the new lines</param>        
        private void AddDropDownItems(Dictionary<String, String> Elements, ToolStripDropDownItem DropDownMenu)
        {

            //Based on our depth, allocate results accordingly.
            int CurDepth = GetDepth(DropDownMenu);
            SortedDictionary<String, Dictionary<String, String>> Elems = new SortedDictionary<string, Dictionary<String, String>>();

            //Go through all elements, and add the appropriate ones into our dictionary
            foreach (KeyValuePair<String, String> Elem in Elements)
            {
                String[] splType = Elem.Key.Split('.', ':');
                if (CurDepth == splType.GetUpperBound(0))
                {
                    ToolStripMenuItem NewItem = (ToolStripMenuItem)DropDownMenu.DropDownItems.Add(splType[CurDepth] + " = " + Elem.Value);
                    NewItem.DropDownItemClicked += new ToolStripItemClickedEventHandler(cms_ItemClicked);
                    if (Elem.Value.Length > 0 && Elem.Value[0] == '#')
                    {
                        NewItem.Tag = MM_Database_CIM.Elements[new MM_rdfID(Elem.Value.Substring(1))];
                        NewItem.Enabled = !DisplayedElements.ContainsKey(MM_Database_CIM.Elements[new MM_rdfID(Elem.Value.Substring(1))]);                        
                    }
                    else
                        NewItem.Enabled = false;
                    NewItem.Tag = Elem;
                }
                else if (CurDepth == 1)
                {
                    if (!Elems.ContainsKey(splType[0] + ":" + splType[1]))
                        Elems.Add(splType[0] + ":" + splType[1], new Dictionary<String, String>(10));
                    Elems[splType[0] + ":" + splType[1]].Add(Elem.Key, Elem.Value);
                }
                else
                {
                    if (!Elems.ContainsKey(splType[CurDepth]))
                        Elems.Add(splType[CurDepth], new Dictionary<String, String>(10));
                    Elems[splType[CurDepth]].Add(Elem.Key, Elem.Value);
                }
            }

            foreach (KeyValuePair<String, Dictionary<String, String>> Elem in Elems)
            {
                ToolStripMenuItem NewItem = (ToolStripMenuItem)DropDownMenu.DropDownItems.Add(Elem.Key + (Elem.Value.Count > 0 ? " (" + Elem.Value.Count.ToString("#,##0") + ")" : ""));
                NewItem.Tag = Elem.Value;
                NewItem.DropDownItems.Add("");
                NewItem.DropDownOpening += new EventHandler(NewItem_DropDownOpening);
                NewItem.DropDownItemClicked += new ToolStripItemClickedEventHandler(cms_ItemClicked);
            }

        }

        /// <summary>
        /// Determine the depth of a menu item
        /// </summary>
        /// <param name="DropDownMenu"></param>
        /// <returns></returns>
        private int GetDepth(ToolStripItem DropDownMenu)
        {
            int Depth = 1;
            while ((DropDownMenu = DropDownMenu.OwnerItem) != null)
                Depth++;
            return Depth;
        }

        /// <summary>
        /// Handle a menu item click
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void cms_ItemClicked(object sender, ToolStripItemClickedEventArgs e)
        {
            //Determine our highest-parent menu
            ToolStripItem DropDownMenu = (sender as ToolStripItem);
            if (DropDownMenu == null)
                return;
            else
                while (DropDownMenu.OwnerItem != null)
                    DropDownMenu = DropDownMenu.OwnerItem;
            
            //Determine the clicked CIM element
            if (e.ClickedItem.Text == "Add all")
                foreach (CIM_Element ElemToAdd in (e.ClickedItem.OwnerItem.Tag as CIM_Element[]))
                    AddElement(ElemToAdd);
            else if (e.ClickedItem.Tag is CIM_Element)
                AddElement(e.ClickedItem.Tag as CIM_Element);
            else if (e.ClickedItem.Tag is KeyValuePair<String, String> && ((KeyValuePair<String, String>)e.ClickedItem.Tag).Value.StartsWith("#"))
                AddElement(MM_Database_CIM.Elements[new MM_rdfID((((KeyValuePair<String, String>)e.ClickedItem.Tag)).Value.Substring(1))]);                        
            
            //Clear the screen
            splMain.Panel2.Invalidate();
        }

        /// <summary>
        /// Locate or create a new CIM element front end
        /// </summary>
        /// <param name="Link"></param>
        /// <returns></returns>
        private CIM_Element_FrontEnd LocateElementFrontEnd(CIM_Element Link)
        {
            if (Link == null)
                return null;
            else if (DisplayedElements.ContainsKey(Link))
                return DisplayedElements[Link];
            else
            {
                CIM_Element_FrontEnd NewOut = new CIM_Element_FrontEnd(Link);
                NewOut.RecomputeText(btnRdfID.Checked, btnType.Checked, btnName.Checked, btnTerminals.Checked);
                NewOut.MouseDown += CIMElem_MouseDown;
                NewOut.MouseMove += CIMElem_MouseMove;
                NewOut.MouseUp += CIMElem_MouseUp;
                NewOut.MouseClick += CIMElem_MouseClick;
                DisplayedElements.Add(Link, NewOut);
                splMain.Panel2.Controls.Add(NewOut);
                return NewOut;
            }
        }

        /// <summary>
        /// Add an element to the display
        /// </summary>
        /// <param name="ElementToAdd"></param>
        private void AddElement(CIM_Element ElementToAdd)
        {
            //Locate our element
            CIM_Element_FrontEnd Elem = LocateElementFrontEnd(ElementToAdd);
            if (Elem.BaseElement.Type == "cim:Terminal")
                Elem.Visible = btnTerminals.Checked;

            //Check to see if we have any of this element's outgoing links, and if so, add them. Also check to see if a terminal is in between the two
            foreach (CIM_Element ElemToCheck in ElementToAdd.OutLinks)
                if (DisplayedElements.ContainsKey(ElemToCheck) && !Elem.OutgoingElements.Contains(DisplayedElements[ElemToCheck]))
                    Elem.OutgoingElements.Add(DisplayedElements[ElemToCheck]);
                    
            
            //Now check the element's incoming links.
            foreach (CIM_Element ElemToCheck in ElementToAdd.InLinks)
                if (DisplayedElements.ContainsKey(ElemToCheck) && !DisplayedElements[ElemToCheck].OutgoingElements.Contains(Elem))
                    DisplayedElements[ElemToCheck].OutgoingElements.Add(Elem);
                
            //Now, check for terminals
            List<CIM_Element> TerminalsToAdd = new List<CIM_Element>();
            foreach (CIM_Element ElemToCheck in DisplayedElements.Keys)
                if (ElemToCheck != ElementToAdd)
                    foreach (CIM_Element Term1 in ElementToAdd.Links)
                        foreach (CIM_Element Term2 in ElemToCheck.Links)
                            if (Term1.Type == "cim:Terminal" && Term1 == Term2 && !DisplayedElements.ContainsKey(Term1))
                                TerminalsToAdd.Add(Term1);

            foreach (CIM_Element Terminal in TerminalsToAdd)
                AddElement(Terminal);
        }        

        /// <summary>
        /// Handle the mouse up on a CIM element
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void CIMElem_MouseUp(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left)
                (sender as Control).Tag = null;
        }

        /// <summary>
        /// Handle the mouse down on a CIM element
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void CIMElem_MouseDown(object sender, MouseEventArgs e)
        {

            if (e.Button == MouseButtons.Left)
            {
                CIM_Element_FrontEnd Sender = (sender as CIM_Element_FrontEnd);
                Sender.Tag = new Point(Cursor.Position.X - Sender.Left, Cursor.Position.Y - Sender.Top);
                Sender.BringToFront();
            }
        }

        /// <summary>
        /// Handle the mouse down on a CIM element
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void CIMElem_MouseMove(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left)
            {
                CIM_Element_FrontEnd Sender = (sender as CIM_Element_FrontEnd);
                if (Sender.Tag == null)
                {
                    Sender.Tag = new Point(Cursor.Position.X - Sender.Left, Cursor.Position.Y - Sender.Top);
                    Sender.BringToFront();
                }
                Point StartPosition = (Point)Sender.Tag;
                Sender.Location = new Point(Cursor.Position.X - StartPosition.X, Cursor.Position.Y - StartPosition.Y);
                splMain.Panel2.Invalidate();
            }
        }



        #endregion

      

        /// <summary>
        /// Go through all CIM elements, and draw arrows to their outgoing links
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void splMain_Panel2_Paint(object sender, PaintEventArgs e)
        {
            //First, draw all visible links.
            foreach (CIM_Element_FrontEnd FrontEndElement in DisplayedElements.Values)
                if (FrontEndElement.Visible)
                    foreach (CIM_Element_FrontEnd TargetElement in FrontEndElement.OutgoingElements)
                        DrawArrow(FrontEndElement.Bounds, TargetElement.Bounds, e.Graphics,true);
               
           
            //Now, draw all the invisible links around the arrow.
            foreach (CIM_Element_FrontEnd FrontEndElement in DisplayedElements.Values)
                if (!FrontEndElement.Visible)
                    for (int a=0; a < FrontEndElement.OutgoingElements.Count; a++)
                        for (int b=a+1; b < FrontEndElement.OutgoingElements.Count; b++)
                            DrawArrow(FrontEndElement.OutgoingElements[a].Bounds, FrontEndElement.OutgoingElements[b].Bounds, e.Graphics, false);                                                                           
        }

        /// <summary>
        /// Draw an arrow between two points
        /// </summary>
        /// <param name="SourceRect">The starting rectangle</param>
        /// <param name="TargetRect">The target rectangle</param>
        /// <param name="g">The graphics connection</param>
        /// <param name="ArrowEnd">Whether an arrow should appear at the end</param>
        private void DrawArrow(Rectangle SourceRect, Rectangle TargetRect, Graphics g, bool ArrowEnd)
        {
            Point StartPoint = new Point(SourceRect.X + (SourceRect.Width / 2), SourceRect.Y + (SourceRect.Height / 2));
            Point EndPoint = new Point(TargetRect.X + (TargetRect.Width / 2), TargetRect.Y + (TargetRect.Height / 2));

            if (TargetRect.Top > SourceRect.Bottom)
                EndPoint.Y = TargetRect.Top;
            else if (SourceRect.Top > TargetRect.Bottom)
                EndPoint.Y = TargetRect.Bottom;
            if (TargetRect.Left > SourceRect.Right)
                EndPoint.X = TargetRect.Left;
            else if (SourceRect.Left > TargetRect.Right)
                EndPoint.X = TargetRect.Right;

            if (SourceRect.Top > TargetRect.Bottom)
                StartPoint.Y = SourceRect.Top;
            else if (TargetRect.Top > SourceRect.Bottom)
                StartPoint.Y = SourceRect.Bottom;
            if (SourceRect.Left > TargetRect.Right)
                StartPoint.X = SourceRect.Left;
            else if (TargetRect.Left > SourceRect.Right)
                StartPoint.X = SourceRect.Right;

            
            using (Pen OutPen = new Pen(Color.Black, 3f))
            {
                OutPen.StartCap = (ArrowEnd ? LineCap.Round: LineCap.ArrowAnchor);
                OutPen.EndCap = LineCap.ArrowAnchor;
                OutPen.LineJoin = LineJoin.Bevel;
                OutPen.Color = ArrowEnd ? Color.Black : Color.Green;
                g.DrawLine(OutPen, StartPoint, EndPoint);
            }
        }

        

        
        /// <summary>
        /// Handle the user's changing a CIM parameter.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void UpdateVisibility(object sender, EventArgs e)
        {
            ToolStripButton Sender = (ToolStripButton)sender;
            Sender.Checked ^= true; 
            if (Sender != btnTerminals)
                foreach (CIM_Element_FrontEnd Elem in DisplayedElements.Values)
                    Elem.RecomputeText(btnRdfID.Checked, btnType.Checked, btnName.Checked, toolStripButton1.Checked);
            else
                foreach (CIM_Element_FrontEnd Elem in DisplayedElements.Values)
                    if (Elem.BaseElement.Type == "cim:Terminal")
                        Elem.Visible = Sender.Checked;
            this.splMain.Panel2.Refresh();
        }

        /// <summary>
        /// Write an SVG line between two points
        /// </summary>
        /// <param name="xW">The XML text writer</param>
        /// <param name="StartLine">The start of the line</param>
        /// <param name="EndLine">The end of the line</param>
        /// <param name="DualArrows">Whether dual arrows should be used</param>
        private void WriteLine(XmlTextWriter xW, CIM_Element_FrontEnd StartLine, CIM_Element_FrontEnd EndLine, bool DualArrows)
        {
            //Determine our points
            Rectangle SourceRect = StartLine.Bounds;
            Rectangle TargetRect = EndLine.Bounds;
            Point StartPoint = new Point(SourceRect.X + (SourceRect.Width / 2), SourceRect.Y + (SourceRect.Height / 2));
            Point EndPoint = new Point(TargetRect.X + (TargetRect.Width / 2), TargetRect.Y + (TargetRect.Height / 2));

            if (TargetRect.Top > SourceRect.Bottom)
                EndPoint.Y = TargetRect.Top;
            else if (SourceRect.Top > TargetRect.Bottom)
                EndPoint.Y = TargetRect.Bottom;
            if (TargetRect.Left > SourceRect.Right)
                EndPoint.X = TargetRect.Left;
            else if (SourceRect.Left > TargetRect.Right)
                EndPoint.X = TargetRect.Right;

            if (SourceRect.Top > TargetRect.Bottom)
                StartPoint.Y = SourceRect.Top;
            else if (TargetRect.Top > SourceRect.Bottom)
                StartPoint.Y = SourceRect.Bottom;
            if (SourceRect.Left > TargetRect.Right)
                StartPoint.X = SourceRect.Left;
            else if (TargetRect.Left > SourceRect.Right)
                StartPoint.X = SourceRect.Right;


            xW.WriteStartElement("line");
            xW.WriteAttributeString("x1", StartPoint.X.ToString());
            xW.WriteAttributeString("y1", StartPoint.Y.ToString());
            xW.WriteAttributeString("x2", EndPoint.X.ToString());
            xW.WriteAttributeString("y2", EndPoint.Y.ToString());
            xW.WriteAttributeString("stroke", "black");
            xW.WriteAttributeString("stroke-width", "3");
            if (DualArrows)
                xW.WriteAttributeString("marker-start", "url(#startArrow)");
            xW.WriteAttributeString("marker-end", "url(#endArrow)");
            xW.WriteEndElement();
        }


        /// <summary>
        /// Export the current workspace as a SVG document
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnSave_Click(object sender, EventArgs e)
        {
            //Save an SVG file

            using (SaveFileDialog s = new SaveFileDialog())
            {
                s.Title = "Save SVG of CIM topology";
                s.Filter = "SVG Graphics File (*.svg)|*.svg";
                if (s.ShowDialog() == DialogResult.OK)
                    using (XmlTextWriter xW = new XmlTextWriter(s.FileName, Encoding.UTF8))
                    {
                        xW.Formatting = Formatting.Indented;
                        xW.WriteStartDocument(false);
                        xW.WriteDocType("svg", "-//W3C//DTD SVG 1.1//EN", "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd", null);
                        xW.WriteStartElement("svg", "http://www.w3.org/2000/svg");
                        xW.WriteAttributeString("width", "100%");
                        xW.WriteAttributeString("height", "100%");
                        xW.WriteAttributeString("version", "1.1");

                        xW.WriteStartElement("defs");
                        xW.WriteStartElement("marker");
                        xW.WriteAttributeString("id", "endArrow");
                        xW.WriteAttributeString("viewBox", "0 0 10 10");
                        xW.WriteAttributeString("refX", "1");
                        xW.WriteAttributeString("refY", "5");
                        xW.WriteAttributeString("markerUnits", "strokeWidth");
                        xW.WriteAttributeString("orient", "auto");
                        xW.WriteAttributeString("markerWidth", "5");
                        xW.WriteAttributeString("markerHeight", "4");
                        xW.WriteStartElement("polyline");
                        xW.WriteAttributeString("points", "0,0 10,5 0,10 1,5"); 
                        xW.WriteAttributeString("fill", "black");
                        xW.WriteEndElement();
                        xW.WriteEndElement();
                        xW.WriteStartElement("marker");
                        xW.WriteAttributeString("id", "startArrow");
                        xW.WriteAttributeString("viewBox", "0 0 10 10");
                        xW.WriteAttributeString("refX", "1");
                        xW.WriteAttributeString("refY", "5");
                        xW.WriteAttributeString("markerUnits", "strokeWidth");
                        xW.WriteAttributeString("orient", "auto");
                        xW.WriteAttributeString("markerWidth", "5");
                        xW.WriteAttributeString("markerHeight", "4");
                        xW.WriteStartElement("polyline");
                        xW.WriteAttributeString("points", "10,0 0,5 10,10 9,5"); 
                        xW.WriteAttributeString("fill", "black");
                        xW.WriteEndElement();
                        xW.WriteEndElement();
                        xW.WriteEndElement();

          

                        //First, write out our arrows
                        foreach (CIM_Element_FrontEnd LineToWrite in DisplayedElements.Values)
                            if (LineToWrite.Visible)                           
                                foreach (CIM_Element_FrontEnd OtherSide in LineToWrite.OutgoingElements)
                                    if (OtherSide.Visible)
                                        WriteLine(xW, LineToWrite, OtherSide, false);
                            
                        //Now, write out our terminals
                        foreach (CIM_Element_FrontEnd LineToWrite in DisplayedElements.Values)
                            if (!LineToWrite.Visible)
                                for (int a = 0; a < LineToWrite.OutgoingElements.Count; a++)
                                    for (int b = a + 1; b < LineToWrite.OutgoingElements.Count; b++)
                                        WriteLine(xW, LineToWrite.OutgoingElements[a], LineToWrite.OutgoingElements[b], true);                                                                                                                                                                    

                        //Now, write out each element.
                        foreach (CIM_Element_FrontEnd ElemToWrite in DisplayedElements.Values)
                            if (ElemToWrite.Visible)
                        {
                            xW.WriteStartElement("rect");
                            xW.WriteAttributeString("x", ElemToWrite.Left.ToString());
                            xW.WriteAttributeString("y", ElemToWrite.Top.ToString());
                            xW.WriteAttributeString("width", ElemToWrite.Width.ToString());
                            xW.WriteAttributeString("height", ElemToWrite.Height.ToString());
                            xW.WriteAttributeString("fill", ColorTranslator.ToHtml(ElemToWrite.BackColor));
                            xW.WriteEndElement();

                            xW.WriteStartElement("text");
                            xW.WriteAttributeString("x", ElemToWrite.Left.ToString());
                            xW.WriteAttributeString("y", ElemToWrite.Top.ToString());
                            xW.WriteAttributeString("fill", ColorTranslator.ToHtml(ElemToWrite.ForeColor));
                            xW.WriteAttributeString("font-size",  ElemToWrite.Font.SizeInPoints.ToString() + "pt");
                            xW.WriteAttributeString("text-anchor", "middle");
                            xW.WriteAttributeString("font-family", ElemToWrite.Font.FontFamily.Name);
                            
                            String[] LinesToWrite = ElemToWrite.Text.Split('\n');
                            for (int a=0; a < LinesToWrite.Length; a++)
                            {                                
                                xW.WriteStartElement("tspan");
                                xW.WriteAttributeString("x", (ElemToWrite.Left + (ElemToWrite.Width/2)).ToString());
                                if (a == 0)
                                    xW.WriteAttributeString("y", (ElemToWrite.Top + ElemToWrite.Font.Height).ToString());
                                else
                                    xW.WriteAttributeString("dy", ElemToWrite.Font.Height.ToString());
                                xW.WriteString(LinesToWrite[a].Trim());
                                xW.WriteEndElement();
                            }
                            xW.WriteEndElement();
                        }
                        xW.WriteEndElement();
                        xW.Flush();
                        xW.Close();

                    }
                MessageBox.Show("SVG export complete", Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Information);
            }
        }

        /// <summary>
        /// Clear the workspace
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnClear_Click(object sender, EventArgs e)
        {
            lock (this)
            {
                foreach (CIM_Element ElemToDispose in DisplayedElements.Keys)
                    DisplayedElements[ElemToDispose].Dispose();                    
                DisplayedElements.Clear();
                splMain.Panel2.Controls.Clear();
                this.Invalidate();
                this.Refresh();
            }
        }

      
    }
}